package org.windowkit.toolkit.widget.table;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.print.PrinterException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultCellEditor;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.ScrollPaneLayout;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.text.JTextComponent;

import org.ddevil.data.Filter;
import org.ddevil.data.SortOrder;
import org.windowkit.toolkit.event.EventDispatcher;
import org.windowkit.toolkit.print.PrintUtility;
import org.windowkit.toolkit.print.PrintableComponent;
import org.windowkit.toolkit.util.ComponentUtils;
import org.windowkit.toolkit.util.FormatUtils;
import org.windowkit.toolkit.widget.table.event.DataRequestedEvent;
import org.windowkit.toolkit.widget.table.event.DataRequestedListener;
import org.windowkit.toolkit.widget.table.event.TableEditedEvent;
import org.windowkit.toolkit.widget.table.event.TableEditedListener;
import org.windowkit.toolkit.widget.table.render.LongRenderer;
import org.windowkit.toolkit.widget.table.render.SortTableHeaderRenderer;


/**
 * Base class for Table Pane Widgets.
 *
 * @author Eric Lundin
 * @author Rich
 */
public abstract class AbstractTablePane extends JComponent implements
		PrintableComponent, ComponentListener, TableEditedListener {

	private static final long serialVersionUID = -6320053133082960388L;

	/**
	 *
	 */
	private static final String TABLE_HELP_TITLE = "Table Help";

	/**
	 * The error message to display when we are unable to remove a given column
	 * due to it being the last column
	 */
	protected static final String REMOVE_COLUMN_ERROR_MESSAGE = "Unable to remove this column as no columns would remain.";

	/**
	 * The title/action command for viewing help about the table and its
	 * preference support.
	 */
	protected static final String DISPLAY_HELP = TABLE_HELP_TITLE;

	/**
	 * The title/action command for removing a column from a context menu
	 */
	protected final static String REMOVE_THIS_COLUMN = "Remove This Column";

	/**
	 * The title/action command for getting the next set of data
	 */
	protected final static String NEXT_DATA = "Get Next Data";

	/**
	 * The title/action command for getting the next set of data
	 */
	protected final static String PREV_DATA = "Get Previous Data";

	/**
	 * The title for the add/remove columns submenu
	 */
	protected final static String ADD_REMOVE_COLUMNS = "Add/Remove Column";

	/**
	 * The title/action command to print this table
	 */
	protected final static String PRINT_TABLE = "Print Table";

	/**
	 * The title/action command for previewing a table print.
	 */
	protected final static String PRINT_PREVIEW = "Print Preview";

	/**
	 * The table.
	 */
	protected Table theTable;

	/**
	 * Stores the rows which were selected before printing.
	 */
	protected int[] printState;

	/**
	 * The popup menu to display.
	 */
	protected JPopupMenu tablePopupMenu;


	/**
	 * Redispatches events as if they originated from this component.
	 */
	protected EventDispatcher eventDispatcher;

	protected FilterRowEditListener filterEditListener;

	/**
	 * Holds menu items for adding and removing columns.
	 */
	protected JMenu subMenu;

	/**
	 * Menu item to remove the currently selected column.
	 */
	protected JMenuItem removeThisColumn;

	/**
	 * Menu to be displayed on the table's header.
	 */
	protected JPopupMenu headerMenu;

	/**
	 * Listens for changes to the column model.
	 */
	protected MyTableColumnModelListener columnModelListener;

	/**
	 * Object which will handle providing more data when the next dataset button
	 * is pressed.
	 */
	protected Vector<DataRequestedListener> dataRequestedListeners;

	/**
	 * Listens for mouse events.
	 */
	protected TablePaneMouseListener mouseListener;

	/**
	 * The scroll pane to display the table in.
	 */
	protected JScrollPane scrollPane;

	/**
	 * Sends an event to get the previous set of data.
	 */
	protected JButton previousDataButton;

	/**
	 * Sends an event to get the next set of data.
	 */
	protected JButton nextDataButton;

	/**
	 * Listens for events on the popupmenus.
	 */
	protected TablePaneActionListener menuActionListener;

	/**
	 * The table model which will store our data.
	 */
	protected SortableTableModel model;

	/** Tracks user chosen column sizes */
	protected int[] userChosenColSizes;

	/**
	 * Holds the edited table listeners.
	 */
	protected ArrayList<TableEditedListener> editListenerList;

	/**
	 * True if we should automatically resize columns.
	 */
	private boolean autoResizeColumns = true;

	/**
	 * Stores whether we should currently allow resizing. Mostly used to stop
	 * resizing during programatic changes to the table's structure.
	 */
	protected boolean resizeFlag = true;

	/**
	 *
	 */
	public AbstractTablePane(final SortableTableModel m) {

		this.model = m;
		this.model.addTableEditedListener(this);

		initThisComponent();
		initTableControls();
		setPopupMenu(null);
		setDefaultSorterAttributes();
	}

	/**
	 * Initialize this component.
	 */
	protected void initThisComponent() {
		editListenerList = new ArrayList<TableEditedListener>();
		dataRequestedListeners = new Vector<DataRequestedListener>();

		setLayout(new BorderLayout());
		this.add(initToolbar(), BorderLayout.NORTH);
		this.add(initTable(), BorderLayout.CENTER);
		this.addComponentListener(this);
		setOpaque(true);
		setInsets(2, 2, 2, 2);
	}

	/**
	 * Initialize the table.
	 *
	 * @return The table, placed into a {@link JScrollPane}
	 */
	protected JScrollPane initTable() {
		theTable = new Table(model);

		theTable.getTableHeader().setDefaultRenderer(new SortTableHeaderRenderer(theTable));
		setSelectionType(TableSelectionType.SINGLE_SELECTION);
		theTable.setAutoCreateColumnsFromModel(false);
		theTable.setCellSelectionEnabled(false);
		theTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		columnModelListener = new MyTableColumnModelListener();
		theTable.getColumnModel().addColumnModelListener(columnModelListener);
		// theTable.setDefaultRenderer(String.class, new FilterTableCellRenderer());
		// theTable.setDefaultRenderer(Object.class, new FilterTableCellRenderer());
		// theTable.setDefaultEditor(String.class, new FilterTableCellEditor());
		//

		theTable.setDefaultRenderer(Long.class, new LongRenderer());

		// (DateRenderer)theTable.getDefaultRenderer(Date.class);

		scrollPane = new JScrollPane(theTable);
		scrollPane.setLayout(new LeftCornerScrollPaneLayout());
		scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
//		theTable.setFont();

		theTable.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(final KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_TAB) {
					theTable.transferFocus();
					e.consume();
				}
			}
		});

		return scrollPane;
	}

	/**
	 * Initialize the various controls for the table.
	 */
	protected void initTableControls() {

		/* Event dispatchers */
		eventDispatcher = new EventDispatcher(this);

		// only dispatch events if a valid row was clicked.
		// A valid row is a row with data in it.
		// The extra rows added to fill the table's space are not valid rows.
		eventDispatcher.setMouseEventFilter(new Filter<MouseEvent>() {
			public boolean accept(final MouseEvent arg) {
				int maximumRow = model.getDataRowCount();
				if (model.isFilterable()) {
					maximumRow++;
				}
				return !(theTable.rowAtPoint(arg.getPoint()) >= maximumRow);
			}
		});

		theTable.addMouseListener(eventDispatcher);
		theTable.getSelectionModel().addListSelectionListener(eventDispatcher);

		/* our own event handlers */
		menuActionListener = new TablePaneActionListener();
		mouseListener = new TablePaneMouseListener();
		theTable.getTableHeader().addMouseListener(mouseListener);
		theTable.addMouseListener(mouseListener);

		initHeaderMenu();
		initCornerButtons();
	}

	/**
	 * Initialize the header menu for the table.
	 */
	protected void initHeaderMenu() {
		JMenuItem currentItem;
		headerMenu = new JPopupMenu();
		removeThisColumn = new JMenuItem(REMOVE_THIS_COLUMN);
		removeThisColumn.addActionListener(menuActionListener);
		headerMenu.add(removeThisColumn);

		subMenu = new JMenu(ADD_REMOVE_COLUMNS);
		JCheckBoxMenuItem columnVisible;
		// for each column add a checkbox menu item
		// to allow addition/removal of the column
		for (int i = 0; i < model.getColumnCount(); i++) {
			String colName = model.getColumnName(i);
			columnVisible = new JCheckBoxMenuItem(FormatUtils
					.removeStringFormatting(colName), true);
			columnVisible.setActionCommand(String.valueOf(i));
			columnVisible.addActionListener(menuActionListener);
			subMenu.add(columnVisible);
		}
		headerMenu.add(subMenu);
		headerMenu.addSeparator();

		currentItem = new JMenuItem("Save");
		headerMenu.add(currentItem);
		currentItem.addActionListener(menuActionListener);
		currentItem = new JMenuItem("Load");
		headerMenu.add(currentItem);
		currentItem.addActionListener(menuActionListener);
	}

	protected JComponent initToolbar() {
		Action actReverseFilter = new AbstractAction() {
			private boolean negate = false;
			public void actionPerformed(final ActionEvent e) {
				negate = !negate;
				putValue(Action.NAME, negate ? "!!" : "!");
				model.setNegateFilter(negate);
			}
		};
		ComponentUtils.configureAction(actReverseFilter, "!", "Negate the current filter criteria", (ImageIcon)null);
		JToolBar toolbar = ComponentUtils.createToolbar(new Insets(1, 1, 1, 1), true, actReverseFilter);
		return toolbar;
	}

	/**
	 * Initializes the next data set buttons which are displayed in the right
	 * corners of the scrollpane. The previous dataset button is added when
	 * setAllowPreviousDataTraversal is called.
	 */
	private void initCornerButtons() {
		Icon dataTraversalIcon = UIManager.getIcon("FilterTable.icon");
		// Lower right corner
		nextDataButton = new JButton();
		nextDataButton.setToolTipText("<html>Next Data</html>");
		nextDataButton.addActionListener(menuActionListener);
		nextDataButton.addMouseListener(mouseListener);
		scrollPane.setCorner(ScrollPaneConstants.LOWER_RIGHT_CORNER, nextDataButton);
		nextDataButton.setIcon(dataTraversalIcon);
	}

	public void setAllowPreviousDataTraversal(final boolean allow) {

		Icon dataTraversalIcon;
		if (allow) {
			dataTraversalIcon = UIManager.getIcon("FilterTable.icon");
			previousDataButton = new JButton();
			previousDataButton.setToolTipText("Previous Data");
			previousDataButton.addActionListener(menuActionListener);
			previousDataButton.addMouseListener(mouseListener);
			previousDataButton.setActionCommand(PREV_DATA);
			previousDataButton.setIcon(dataTraversalIcon);
			previousDataButton.setPreferredSize(new Dimension((int) scrollPane
					.getVerticalScrollBar().getPreferredSize().getWidth(),
					(int) scrollPane.getHorizontalScrollBar()
							.getPreferredSize().getHeight()));
			scrollPane.setCorner(ScrollPaneConstants.LOWER_LEFT_CORNER,
					previousDataButton);
		} else {
			// scrollPane.setCorner(JScrollPane., getPrintComponent())
		}
	}

	/**
	 * The default attributes to use for sorting and filtering.
	 */
	protected void setDefaultSorterAttributes() {
		setSortable(true);
		setFilterable(true);
	}

	/**
	 * Builds a column model using all the columns currently contained in our
	 * table model. The columns will be added in the same order they are stored
	 * in our table model.
	 */
	protected void buildDefaultColumnModel() {

		TableColumnModel columnModel = theTable.getColumnModel();
		TableColumn current;

		columnModelListener.setEnabled(false);

		// remove any existing columns
		while (columnModel.getColumnCount() > 0) {
			columnModel.removeColumn(columnModel.getColumn(0));
		}

		for (int i = 0; i < model.getColumnCount(); i++) {
			current = new TableColumn(i);
			current.setHeaderValue(model.getColumnName(i));
			columnModel.addColumn(current);
		}

		columnModelListener.setEnabled(true);
	}

	/**
	 * Call this anytime the set of columns, stored in the table model, has
	 * changed.
	 */
	protected void columnSetChanged() {

		// recreate the header menu
		initHeaderMenu();
	}


	/**
	 * Remove the row at the given view index
	 *
	 * @param index
	 *            The index of the row to remove
	 */
	public void removeRow(final int index) {
		model.removeRow(index);
	}


	/**
	 * Clear the table. Including all sorts and filters.
	 */
	public void clearTable() {
		clearTableData(true);
	}

	/**
	 * Clear only the data from the table.
	 */
	public void clearTableData() {
		clearTableData(false);
	}

	/**
	 * Clear only the data from the table. Optionally clear the filter criteria
	 * as well.
	 *
	 * @param clearFilters
	 *            True to clear any filter criteria. False otherwise.
	 */
	public void clearTableData(final boolean clearFilters) {
		if (theTable.isEditing()) {
			theTable.getCellEditor().cancelCellEditing();
		}
		model.clearTableData(clearFilters);
	}

	/**
	 * Clear all the current sorts.
	 */
	public void clearSorts() {
		model.clearAllSorts();
	}


	public void clearValue() {
		clearSelection();
	}

	/**
	 * Are any rows in this table selcted.
	 *
	 * @return
	 */
	public boolean isDataSelected() {
		return getSelectedRows().size() != 0;
	}

	/**
	 * Clear the current table selection.
	 */
	public void clearSelection() {
		theTable.clearSelection();
	}

	/**
	 * Returns the currently selected in the table. Returns an empty array if no
	 * table rows are currently selected.
	 *
	 * @return The rows selected(highlighted) by the user.
	 */
	public ArrayList<Integer> getSelectedRows() {
		ArrayList<Integer> sRows = new ArrayList<Integer>();
		int[] selRows = theTable.getSelectedRows();
		int minimumRow = 0;
		int maximumRow = model.getDataRowCount() - 1;

		if (model.isFilterable()) {
			minimumRow = 1;
			maximumRow++;
		}
		for (int selRow : selRows) {
			if (selRow <= maximumRow && selRow >= minimumRow) {
				sRows.add(selRow);
			}
		}
		return sRows;
	}

	public int getSelectedRowCount() {
		return getSelectedRows().size();
	}

	/**
	 * Gets the indicies of the selected rows in the table.
	 *
	 * @return The indicies of the selected rows in the table.
	 */
	public int[] getSelectedRowsArray() {
		ArrayList<Integer> selectedRows = getSelectedRows();
		int[] selRows = new int[selectedRows.size()];

		for (int i = 0; i < selRows.length; i++) {
			selRows[i] = selectedRows.get(i);
		}
		return selRows;
	}

	/**
	 * Set the given rows as selected.
	 *
	 * @param rows
	 *            The row indicies which will be selected.
	 */
	public void setSelectedRows(final int[] rows) {
		theTable.clearSelection();
		for (int row : rows) {
			theTable.getSelectionModel().addSelectionInterval(row, row);
		}
		if (rows.length == 1) {
			Rectangle r = theTable.getCellRect(rows[0], 0, true);
			JViewport viewport = scrollPane.getViewport();
			if (viewport != null) {
				boolean shouldScroll = !viewport.getViewRect().intersects(r);
				if (shouldScroll) {
					theTable.scrollRectToVisible(r);
				}
			}
		}
	}

	/**
	 * Increment the selection by one.
	 *
	 * @return True if the selection was changed.
	 */
	public boolean incrementSelection() {
		return modifySelection(1);
	}

	public void setShowNextDataButton(final boolean showNexDataButton) {
		nextDataButton.setEnabled(showNexDataButton);
		nextDataButton.setVisible(showNexDataButton);
	}

	/**
	 * Decrement the selection by one.
	 *
	 * @return True if the selection was changed.
	 */
	public boolean decrementSelection() {
		return modifySelection(-1);
	}

	/*
	 * Adjust the current selection index by the offset.
	 */
	private boolean modifySelection(final int offset) {
		boolean changed = false;
		int minRow;
		int maxRow;
		int selectedIndices[] = getSelectedRowsArray();
		int offsetSelection;
		maxRow = model.getDataRowCount() - 1;
		if (selectedIndices.length == 1) {
			changed = false;
			if (model.isFilterable()) {
				minRow = 1;
				maxRow += 1;
			} else {
				minRow = 0;
			}
			offsetSelection = selectedIndices[0] + offset;
			if (offsetSelection >= minRow && offsetSelection <= maxRow) {
				changed = true;
				setSelectedRows(new int[] { offsetSelection });
			}
		}
		return changed;
	}

	/**
	 * Set the type of selection this table will allow.
	 *
	 * @param selType
	 *            The type of selection to use.
	 */
	public void setSelectionType(final TableSelectionType selType) {
		theTable.setSelectionType(selType);
	}

	public void setColumnCellEditor(final int columnIndex, final TableCellEditor editor) {
		// TODO this isnt right
		// if(columnIndex > -1){
		// theTable.getColumn(columnIndex).setCellEditor(editor);
		// }
	}

	/**
	 * Set the comparator by {@link Class}.
	 *
	 * @param className
	 *            The {@link Class} to apply this {@link Comparator} to.
	 * @param myComparator
	 *            The {@link Comparator} to use for this class.
	 */
	public void setColumnComparator(final Class className, final Comparator myComparator) {
		model.setComparator(className, myComparator);
	}

	/**
	 * Set the comparator for the column at the given index.
	 *
	 * @param colIndex
	 * @param myComparator
	 */
	public void setColumnComparator(final int colIndex, final Comparator myComparator) {
		model.setComparator(colIndex, myComparator);
	}

	/**
	 * Set the widths of the table columns.
	 *
	 * @param columnWidths
	 *            The widths the columns should display at.
	 * @deprecated need to be able to do this by 'id' or embed info in display descriptor
	 */
	@Deprecated
	public void setColumnWidths(final int... columnWidths) {
		TableColumnModel tcm = theTable.getColumnModel();
		TableColumn column;
		for (int i = 0; i < tcm.getColumnCount(); i++) {
			column = tcm.getColumn(i);
			column.setPreferredWidth(columnWidths[column.getModelIndex()]);
		}
		// userChosenColSizes = columnWidths;
	}

	/**
	 * Sets whether to allow sorting on this table
	 *
	 * @param isSortable
	 *            True to allow sorting false otherwise.
	 */
	public void setSortable(final boolean isSortable) {
		model.setSortable(isSortable);
	}

	/**
	 * Set whether this table will support filtering.
	 *
	 * @param isFilterable
	 *            True if the table will allow filtering. False otherwise.
	 */
	public void setFilterable(final boolean isFilterable) {
		model.setFilterable(isFilterable);
		JTextField editorComponent = (JTextField) ((DefaultCellEditor) theTable
				.getCellEditor(0, 0)).getComponent();
		if (isFilterable) {
			if (filterEditListener == null) {
				filterEditListener = new FilterRowEditListener();
			}
			((DefaultCellEditor) theTable.getCellEditor(0, 0))
					.setClickCountToStart(1);
			editorComponent.getDocument().addDocumentListener(
					filterEditListener);
			editorComponent.setBackground(new Color(200, 200, 255));
		}
		if (!isFilterable) {
			TableCellEditor theCellEditor;
			if ((theCellEditor = theTable.getCellEditor()) != null) {
				theCellEditor.cancelCellEditing();
			}
			editorComponent.getDocument().removeDocumentListener(
					filterEditListener);
		}
	}

	/**
	 * Set the popup menu to be used on the table. Common functionality will be
	 * added to the given menu.
	 *
	 * @param tablePopupMenu
	 *            The popup menu to use.
	 */
	public void setPopupMenu(final JPopupMenu tablePopupMenu) {
		JMenuItem currentItem;
		// We can pass a null table menu to only include the default table popup
		// options
		if (tablePopupMenu == null) {
			this.tablePopupMenu = new JPopupMenu();
		} else {
			this.tablePopupMenu = tablePopupMenu;
			this.tablePopupMenu.addSeparator();
		}
		currentItem = new JMenuItem(PRINT_PREVIEW);
		currentItem.addActionListener(menuActionListener);
		this.tablePopupMenu.add(currentItem);

		currentItem = new JMenuItem(PRINT_TABLE);
		currentItem.setActionCommand(PRINT_TABLE);
		currentItem.addActionListener(menuActionListener);
		this.tablePopupMenu.add(currentItem);

		currentItem = new JMenuItem(DISPLAY_HELP);
		currentItem.addActionListener(menuActionListener);
		this.tablePopupMenu.add(currentItem);

		currentItem = new JMenuItem("Count");
		currentItem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				JOptionPane.showMessageDialog(AbstractTablePane.this,
						getSelectedRowCount(),
						"Selected Row Count",
						JOptionPane.INFORMATION_MESSAGE);
			}
		});
		this.tablePopupMenu.add(currentItem);

	}

	/**
	 * Set whether to automatically resize the columns.
	 *
	 * @param autoResizeColumns
	 *            True to auto resize columns, false otherwise.
	 */
	public void setAutoResizeColumns(final boolean autoResizeColumns) {
		this.autoResizeColumns = autoResizeColumns;
	}

	/**
	 * Sets the space between this component and any other component above,
	 * below, or next to it.
	 *
	 * @param top
	 *            The top inset.
	 * @param left
	 *            The left inset.
	 * @param bottom
	 *            The bottom inset.
	 * @param right
	 *            The right inset.
	 */
	public void setInsets(final int top, final int left, final int bottom, final int right) {
		setBorder(BorderFactory.createEmptyBorder(top, left, bottom, right));
	}

	/**
	 * Set whether to display the table header.
	 *
	 * @param showHeader
	 *            True to show header. False otherwise.
	 */
	public void setShowTableHeader(final boolean showHeader) {

		// TODO There are a few ways to achieve this.
		// Setting the 'table' header to null will cause problems
		// in subclasses where getTableHeader() is used and expects a non null.
		// Setting the scrollpane column header to null won't allways work
		// since the scroll pane column header isn't set as the table header
		// until the scroll pane has been added to an awt component.

		// This isn't perfect but will do for now.
		// We will hide the header but the space is still there.
		theTable.getTableHeader().setVisible(showHeader);

		// if (showHeader) {
		// scrollPane.setColumnHeader(originalHeader);
		// } else {
		// originalHeader = scrollPane.getColumnHeader();
		// scrollPane.setColumnHeader(null);
		// }
	}

	/**
	 * Create the text to display in the table's help menu.
	 *
	 * @return The text to display in the table's help menu.
	 */
	protected StringBuilder createHelpText() {
		StringBuilder helpText = new StringBuilder();

		if (model.isSortable()) {
			helpText.append("<H2>Sorting</H2>");
			helpText.append("<UL>");
			helpText
					.append("<LI>Double click any column header to sort the table by that column.</LI>");
			helpText
					.append("<LI>Double clicking an already sorted column will reverse the direction of that sort.</LI>");
			helpText
					.append("<LI>Once a column is selected holding control (CTRL) and double clicking another column will<BR> ");
			helpText
					.append("create a compound sort, sorting first by the initial column and resolving any equivelencies<BR>");
			helpText.append("with the secondary sorts.</LI>");
			helpText
					.append("<LI>A compound sort may be defined with as many levels as there are columns. <BR>Make sure control is held down when selecting each column after the intial column.");
			helpText
					.append("<LI>The sort direction of any column in a compound sort may be reversed at any time by holding<BR>control and double clicking the sorted column</LI>");
			helpText.append("</UL>");
		}
		if (model.isFilterable()) {
			helpText.append("<h2>Filtering</h2>");
			helpText.append("<UL>");
			helpText
					.append("<LI>Enter any string in the first row to filter by that string.</LI>");
			helpText.append("</UL>");
		}
		helpText.append("<H2>User Preferences</H2>");

		helpText.append("<UL>");
		helpText.append("<LI><STRONG>Loading and Saving</STRONG></LI>");

		helpText.append("<UL>");
		helpText
				.append("<LI>When a given table is loaded the application will check to see if you have any table preferences.<BR>If you have table preferences stored for this table they will be loaded.</LI>");
		helpText
				.append("<LI>Save the current column preferences by clicking the menu in the top right corner of the table and<BR>then choosing the \"Save Table Layout\" option.</LI>");
		helpText
				.append("<LI>The table's default layout may be reloaded by clicking the menu in the top right corner of the table<BR> and then choosing the \"Load Default Layout\" option.</LI>");
		helpText.append("</UL>");

		helpText.append("<LI><STRONG>Configuring Columns</STRONG></LI>");
		helpText.append("<UL>");
		helpText
				.append("<LI>Remove a column by right clicking on its header and select Remove Column.</LI>");
		helpText
				.append("<LI>Add a column by clicking the menu in the top right corner of the table and then choose the<BR>\"Add/Remove Column\" sub menu. Then place a check mark in the option of the column you wish to add. <BR>(Columns can also be removed in this way)</LI>");
		helpText
				.append("<LI>Resize a column's position the mouse cursor between the right side of the column to be<BR>resized and the column to the right. Click and drag the column to the desired size.</LI>");
		helpText
				.append("<LI>Reorder the columns click and drag any column's header to the desired location.</LI>");
		helpText.append("</ul>");
		helpText.append("</ul>");

		helpText.append("<H2>Printing the Table</H2>");
		helpText.append("<UL>");
		helpText.append("<LI><STRONG>Setup</STRONG></LI>");
		helpText.append("<UL>");
		helpText
				.append("<LI>The table will print to paper the same way it is currently displayed. This means that the table can be printed with the following modificaions: <UL><LI>Fiiltered Data</LI><LI>Sorted Data</LI><LI>Removed Columns</LI><LI>Reordered Columns</LI><LI>Resized Columns</LI></UL></LI>");
		helpText.append("</UL>");
		helpText.append("<LI><STRONG>Printing</STRONG></LI>");
		helpText.append("<UL>");
		helpText
				.append("<LI>Initiate the printing process by right clicking on the table data and selecting the \"Print Table\" option.</LI>");
		helpText
				.append("<LI>The print dialog will display. Set the printer and paper options to use in printing the table.</LI>");
		helpText
				.append("<LI>Press the \"Print\" button to send the table to the printer or press the \"Cancel\" button to abort the print process and return to the table.</LI>");

		helpText.append("</UL>");
		helpText.append("<LI><STRONG>Print Preview</STRONG></LI>");
		helpText.append("<UL>");
		helpText
				.append("<LI>Open the \"Print Preview\" window by right clicking on the table data and selecting the \"Print Preview\" option.</LI>");
		helpText
				.append("<LI>The \"Print Preview\" window will open, select the default printer, set the default page format, and display the first page.<UL><LI>NOTE: If there is no default printer set in the operating system the first printer discovered will be initially set.</LI></UL></LI>");
		helpText
				.append("<LI>Use the page control in the top right corner of the screen to traverse through the rendered pages. <UL><LI>The arrow buttons on the left and right with a line next to them jump to the first and last pages, respectively.</LI><LI>The plain arrow buttons, to the direct left and right of the text box, jump to the previous and next page respectively.</LI><LI>Page numbers may be entered directly into the textbox in the center of the control. Press the Enter key or transfer focus to a different part of the window to have the change take effect. Entering a page number not in the range of pages causes the first or last page to be displayed depending on whether the page number entered was less than 1 or more than the total number of pages.</LI></UL></LI>");
		helpText
				.append("<LI>Change the currently selected printer by using the \"Printer\" drop down menu.</LI>");
		helpText
				.append("<LI>Change the page format by pressing the \"Format\" button. This will bring up the page format dialog. Choose any desired page format options and press \"OK\" to apply the changes to the rendering of the pages to be printed. Press \"Cancel\" to abort making any changes.</LI>");
		helpText
				.append("<LI>When happy with the results of the preview using the given printer and page format. Press the \"Print\" button.</LI>");
		helpText
				.append("<LI>The Print Dialog will display with the options selected in the \"Print Preview\" window already set.<UL><LI>NOTE: Changes may be made in the Print Dialog after previewing but these changes may cause the table to print differently than just previewed.</LI></UL></LI>");
		helpText.append("</UL>");
		helpText.append("<LI><STRONG>Notes</STRONG></LI>");
		helpText.append("<UL>");
		helpText
				.append("<LI>While in the \"Print Preview\" window the window containing the table being previewed may not be accessed. A message will be displayed requesting the \"Print Preview\" window be closed before working in the given window.</LI>");
		helpText
				.append("<LI>The print system will remove any selection(s) currently on the table and restore them after printing is complete.</LI>");
		helpText
				.append("<LI>The table will always be printed so that no rows are cutoff at the end of a page.</LI>");
		helpText
				.append("<LI>Columns may be cutoff if they extend beyond the end of a page. Take advantage of the \"Print Preview\" window to ensure no data is cutoff before printing.</LI>");
		helpText.append("</UL>");
		helpText.append("</UL>");
		return helpText;
	}

	/**
	 * Called to print the table.
	 *
	 * @param preview
	 *            True to show the preview window. False otherwise.
	 */
	protected void printTable(final boolean preview) {
		PrintUtility print;
		Component root;
		String windowTitle = null;
		root = SwingUtilities.getAncestorOfClass(JInternalFrame.class, this);
		if (root != null && root instanceof JInternalFrame) {
			windowTitle = ((JInternalFrame) root).getTitle();
		}
		print = new PrintUtility(this, windowTitle, preview);

		try {
			print.print();
		} catch (PrinterException pe) {

		}
	}

	/*
	 * PrintableComponent implementation.
	 */

	/*
	 * (non-Javadoc)
	 *
	 * @see org.windowkit.toolkit.print.PrintableComponent#getPrintComponent()
	 */
	public JComponent getPrintComponent() {
		return theTable;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.windowkit.toolkit.print.PrintableComponent#getPrintHeader()
	 */
	public JComponent getPrintHeader() {
		return theTable.getTableHeader();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.windowkit.toolkit.print.PrintableComponent#restorePrePrintState()
	 */
	public void restorePrePrintState() {
		setSelectedRows(printState);
	}

	/**
	 * Prepare the table for printing by storing the selected rows and clearing
	 * the selection.
	 */
	public void prepareForPrint() {
		printState = getSelectedRowsArray();
		theTable.clearSelection();
	}

	/**
	 * Display a message after an exception occurs.
	 *
	 * @param e
	 *            The exception.
	 */
	void handleException(final Exception e) {
		e.printStackTrace();
	}

	/**
	 * Add a visible column to the table. The column must be backed in the table
	 * model using the given model index.
	 *
	 * @param columnIndex
	 *            The table model index of the column to add.
	 */
	protected void addVisibleColumn(final int modelIndex) {

		for (int i = 0; i < theTable.getColumnCount(); i++) {
			if (theTable.getColumn(i).getModelIndex() == modelIndex) {
				return;
			}
		}

		TableColumn tableColumn = new TableColumn(modelIndex);

		theTable.addColumn(tableColumn);
		// currentTablePreference = createTablePreference();

		// attempt to put the new column in the correct place
		if (modelIndex < theTable.getColumnCount()) {
			theTable.moveColumn(theTable.getColumnCount() - 1, modelIndex);
		}

		resizeColumns();
		enableMenusByColumnCount();
	}

	/**
	 * Remove the column with the given model index from our view.
	 *
	 * @param modelIndex
	 *            The table model index of the column to remove
	 */
	protected void removeVisibleColumn(final int modelIndex) {

		// find the column in our column model with the given model index
		for (int i = 0; i < theTable.getColumnCount(); i++) {
			TableColumn theColumn = theTable.getColumn(i);
			if (theColumn.getModelIndex() == modelIndex) {
				removeVisibleColumn(theColumn);
				break;
			}
		}
	}

	/**
	 * Remove the Table Column. If the column is the only column in the column
	 * model it won't be removed and a message will be displayed.
	 *
	 * @param theColumn
	 *            The column to remove.
	 */
	protected void removeVisibleColumn(final TableColumn theColumn) {

		// make sure there will be at least one column left
		if (theTable.getColumnCount() <= 1) {
			JOptionPane.showMessageDialog(this, REMOVE_COLUMN_ERROR_MESSAGE,
					"Error", JOptionPane.ERROR_MESSAGE);
			return;
		}

		// Update preferences menu since we may have come here through a context
		// menu.
		updateUserPreferenceMenu(theColumn.getModelIndex(), false);

		// remove any sorts or filters on the column
		model.setSort(theColumn.getModelIndex(), SortOrder.NONE);
		model.setFilter(theColumn.getModelIndex(), null);
		theTable.removeColumn(theColumn);
		// currentTablePreference = createTablePreference();
		enableMenusByColumnCount();
		resizeColumns();

	}

	/**
	 * Updates the user preference menu to reflect the given column's visible
	 * state.
	 *
	 * @param modelIndex
	 *            The model index of the column.
	 * @param selected
	 *            True if the column is selected. False otherwise.
	 */
	protected void updateUserPreferenceMenu(final int modelIndex, final boolean selected) {
		Component me = subMenu.getMenuComponent(modelIndex);
		((JCheckBoxMenuItem) me).setSelected(selected);
	}

	public void setVisibleRowCount(final int rows) {
		int height = 0;
		for (int row = 0; row < rows; row++) {
			height += theTable.getRowHeight(row)
		/* + theTable.getIntercellSpacing().height */;
		}

		theTable.setPreferredScrollableViewportSize(new Dimension(theTable
				.getPreferredScrollableViewportSize().width, height));
		theTable.invalidate();
	}

	/*
	 * Component Listener implementation. Need to resize our table/columns when
	 * when these events happen.
	 */

	@SuppressWarnings("unused")
	public void componentHidden(final ComponentEvent e) {
		resize();
	}

	@SuppressWarnings("unused")
	public void componentMoved(final ComponentEvent e) {
		resize();
	}

	@SuppressWarnings("unused")
	public void componentResized(final ComponentEvent e) {
		resize();
	}

	@SuppressWarnings("unused")
	public void componentShown(final ComponentEvent e) {
		resize();
	}

	/**
	 * Resize the table .
	 */
	protected void resize() {
		if (!resizeFlag) {
			return;
		}
		JRootPane rootPane = SwingUtilities.getRootPane(this);
		resizeTable();
		resizeColumns();
		if (rootPane != null) {
			rootPane.repaint();
		}
	}

	/**
	 * Resize the columns of the table.
	 */
	protected void resizeColumns() {
		TableColumnModel tcm = theTable.getColumnModel();
		if (!autoResizeColumns || tcm.getColumnCount() == 0) {
			return;
		}
		double viewWidth;
		double scaleFactor;
		int totalColumnWidth = 0;
		int newWidth;
		// int[] activeColumns;
		int totalNewWidth = 0;
		viewWidth = scrollPane.getViewport().getWidth();
		// activeColumns = new int[tcm.getColumnCount()];

		// for (int i = 0; i < tcm.getColumnCount(); i++) {
		// activeColumns[i] = tcm.getColumn(i).getModelIndex();
		// totalColumnWidth += userChosenColSizes[activeColumns[i]];
		// }
		totalColumnWidth = tcm.getTotalColumnWidth();
		if (totalColumnWidth < viewWidth) {
			columnModelListener.setEnabled(false);
			scaleFactor = viewWidth / totalColumnWidth;
			for (int i = 0; i < tcm.getColumnCount(); i++) {
				newWidth = (int) (/* userChosenColSizes[activeColumns[i]] */tcm
						.getColumn(i).getPreferredWidth() * scaleFactor);
				tcm.getColumn(i).setPreferredWidth(newWidth);
				tcm.getColumn(i).setWidth(newWidth);
				totalNewWidth += newWidth;
			}
			tcm.getColumn(tcm.getColumnCount() - 1).setPreferredWidth(
					(int) ((viewWidth - tcm.getTotalColumnWidth()) + tcm
							.getColumn(tcm.getColumnCount() - 1).getWidth()));
			columnModelListener.setEnabled(true);
		}
	}

	/**
	 * When the table is resized this method will be called to change the number
	 * of visible rows.
	 * TODO not for now it won't
	 */
	private void resizeTable() {
//		int[] selectedRows;
//		int width;
//		int height;
//		int totalRowHeight;
//		int headerHeight = theTable.getTableHeader().getHeight();
//		int rowHeight = theTable.getRowHeight();
//		selectedRows = getSelectedRowsArray();
//		int visrows = (int) Math.round(scrollPane.getViewport()
//				.getVisibleRect().getHeight())
//				/ rowHeight;
//		if (!model.isFilterable())
//			visrows += 1;
//		model.setVisibleRows(visrows - 1);
//		setSelectedRows(selectedRows);
//		totalRowHeight = model.getVisibleRows() * rowHeight;
//		width = scrollPane.getViewport().getWidth();
//		height = totalRowHeight;
//		scrollPane.getViewport().setSize(new Dimension(width, height));
//		scrollPane.setSize(new Dimension(width
//				+ scrollPane.getVerticalScrollBar().getWidth(), headerHeight
//				+ totalRowHeight + rowHeight + theTable.getRowMargin() * 2));
	}

	/**
	 * Update the menu selections based on the columns which are currently being
	 * displayed.
	 */
	protected void updateMenuSelections() {
		TableColumnModel columnModel = theTable.getColumnModel();
		ArrayList<Integer> visibleColumns = new ArrayList<Integer>();
		for (int i = 0; i < columnModel.getColumnCount(); i++) {
			visibleColumns.add(columnModel.getColumn(i).getModelIndex());
		}
		for (int i = 0; i < subMenu.getMenuComponentCount(); i++) {
			((JCheckBoxMenuItem) subMenu.getMenuComponent(i))
					.setSelected(visibleColumns.contains(i));
		}
	}

	protected void updateUserColumnSizes() {
		// userChosenColSizes = new
		// int[theTable.getColumnModel().getColumnCount()];
		// for (int i = 0; i < theTable.getColumnModel().getColumnCount(); i++)
		// {
		// userChosenColSizes[theTable.getColumnModel().getColumn(i)
		// .getModelIndex()] = theTable.getColumnModel().getColumn(i)
		// .getWidth();
		// }
	}

	/**
	 * Set the popup menu's current allowed functionality based on how many
	 * columns are present.
	 */
	protected void enableMenusByColumnCount() {
		if (theTable.getColumnCount() > 1) {
			removeThisColumn.setEnabled(true);
			for (int i = 0; i < subMenu.getMenuComponentCount(); i++) {
				subMenu.getMenuComponent(i).setEnabled(true);
			}
		} else {
			removeThisColumn.setEnabled(false);
			int modelIndex = theTable.getColumn(0).getModelIndex();
			subMenu.getMenuComponent(modelIndex).setEnabled(false);
		}
	}

	/**
	 * Delegates {@link TableEditEvent}s from model to developer.
	 */
	public void tableEdited(final TableEditedEvent te) {
		te.setSource(this);
		te.setColumn(theTable.convertColumnIndexToView(te.getEditColumn()));

		for (TableEditedListener current : editListenerList) {
			current.tableEdited(te);
		}
	}

	/**
	 * Add a listener which will be notified when the table is edited by the
	 * user.
	 *
	 * @param tel
	 *            The listener to add.
	 */
	public void addTableEditedListener(final TableEditedListener tel) {
		if (tel != null) {
			editListenerList.add(tel);
		}
	}

	/**
	 * Remove a {@link TableEditedListener}
	 *
	 * @param tel
	 *            The listener to remove.
	 */
	public void removeTableEditedListener(final TableEditedListener tel) {
		editListenerList.remove(tel);
	}

	/**
	 * Adds a ListSelectionListener to the table's selection model.
	 *
	 * @param theListener
	 *            The ListSelectionListener to add.
	 */
	public void addListSelectionListener(final ListSelectionListener theListener) {
		eventDispatcher.addListSelectionListener(theListener);
	}

	/**
	 * Remove a {@link ListSelectionListener} from the table.
	 *
	 * @param theListener
	 *            The listener to remove.
	 */
	public void removeListSelectionListener(final ListSelectionListener theListener) {
		eventDispatcher.removeListSelectionListener(theListener);
	}

	/**
	 * Add a mouse listener to the table.
	 */
	@Override
	public void addMouseListener(final MouseListener listener) {
		eventDispatcher.addMouseListener(listener);
	}

	/**
	 * Remove a mouse listener from this component.
	 */
	@Override
	public void removeMouseListener(final MouseListener l) {
		eventDispatcher.removeMouseListener(l);
	}

	/**
	 * Registers the given listener as a listener for the next and previous data
	 * menu items being selected.
	 *
	 * @param listener
	 *            The DataRequestedListener to add.
	 */
	public void addDataRequestedListener(final DataRequestedListener listener) {
		if (listener != null) {
			dataRequestedListeners.add(listener);
		}
	}

	/**
	 * Remove a {@link DataRequestedListener}.
	 *
	 * @param listener
	 *            The listener to remove.
	 */
	public void removeDataRequestedListener(final DataRequestedListener listener) {
		dataRequestedListeners.remove(listener);
	}

	/**
	 * Notify all {@link DataRequestedListener}s that a request has been made
	 * for the next set of data.
	 */
	protected void dispatchNextDataRequested() {
		// TODO im thinking we should process this through our table model directly, and not use event/listener pattern
		// maybe even move this to a PagingTable extension of our default table pane
		for (DataRequestedListener currentListener : dataRequestedListeners) {
			if (currentListener != null) {
				currentListener.nextDataRequested(new DataRequestedEvent(this));
			}
		}
	}

	/**
	 * Notify all {@link DataRequestedListener}s that a request has been made
	 * for the previous set of data.
	 */
	protected void dispatchPreviousDataRequested() {
		// TODO see above
		for (DataRequestedListener currentListener : dataRequestedListeners) {
			if (currentListener != null) {
				currentListener.previousDataRequested(new DataRequestedEvent(this));
			}
		}
	}

	/**
	 * This class listens for actions on the table's popup menus and the
	 * next/previous data buttons
	 *
	 * @author Eric Lundin
	 */
	protected class TablePaneActionListener implements ActionListener {
		private int xValue;

		/**
		 * Handles the various button/menu clicks associated with the tabel
		 * field.
		 */
		public void actionPerformed(final ActionEvent e) {
			if (e.getSource() == nextDataButton) {
				dispatchNextDataRequested();
			} else if (e.getSource() == previousDataButton) {
				dispatchPreviousDataRequested();
			} else if (e.getActionCommand().equals(REMOVE_THIS_COLUMN)) {
				int viewIndex = theTable.getColumnModel().getColumnIndexAtX(
						xValue);
				removeVisibleColumn(theTable.getColumnModel().getColumn(
						viewIndex).getModelIndex());
			} else if (e.getActionCommand().equals(NEXT_DATA)) {
				dispatchNextDataRequested();
			}
			else if (e.getActionCommand().equals(PREV_DATA)) {
				dispatchPreviousDataRequested();
			}
			else if (e.getActionCommand().equals(PRINT_TABLE)) {
				printTable(false);
			}
			else if (e.getActionCommand().equals(PRINT_PREVIEW)) {
				printTable(true);
			}
			else if (e.getSource() instanceof JCheckBoxMenuItem) {
				// the action command of the menu items is their model index
				int colIndex = Integer.valueOf(e.getActionCommand());
				if (((JCheckBoxMenuItem) e.getSource()).isSelected()) {
					addVisibleColumn(colIndex);
				} else {
					removeVisibleColumn(colIndex);
				}
			}
		}

		/**
		 * Store the value of the x coordinate.
		 *
		 * @param xValue
		 *            The x coordinate of the mouse click.
		 */
		public void setXValue(final int xValue) {
			this.xValue = xValue;
		}
	}

	/**
	 * This class handles the popu menu display logic.
	 *
	 * @author Eric Lundin
	 */
	class TablePaneMouseListener extends MouseAdapter {

		/**
		 * Decides if it is appropriate to display a popup menu based on the
		 * user's mouse click. If the popup command is executed on the header
		 * the remove column option is displayed. If the command is executed on
		 * the table itself the developer provided popup menu is displayed if
		 * one exists.
		 *
		 * @param e
		 *            The MouseEvent passed from the interface method.
		 */
		private void handleMouseAction(final MouseEvent e) {
			Object sourceObject = e.getSource();

			// first handle popup requests
			if (e.isPopupTrigger()) {

				// display the table header popup menu
				if (sourceObject instanceof JTableHeader
						&& (theTable.getColumnModel().getColumnIndexAtX(
								e.getX()) != -1)) {

					menuActionListener.setXValue(e.getX());
					headerMenu.show(e.getComponent(), e.getX(), e.getY());

				}
				// display the table popupmenu
				else if (sourceObject instanceof JTable) {
					if (tablePopupMenu != null) {
						tablePopupMenu.show(e.getComponent(), e.getX(), e
								.getY());
					}
				}
			}
			// check and see if it was a double click on the header
			else if (sourceObject instanceof JTableHeader
					&& e.getClickCount() == 2 && model.isSortable()
					&& e.getButton() == MouseEvent.BUTTON1) {

				// TODO implement resetting selection
				// int[] selectedRows = getSelectedModelRows();
				theTable.clearSelection();

				sortEvent(e);

				// if (selectedRows.length > 0)
				// reselectRows(selectedRows);
			}
		}

		private void sortEvent(final MouseEvent e) {

			TableColumnModel columnModel = theTable.getTableHeader()
					.getColumnModel();
			int viewIndex = columnModel.getColumnIndexAtX(e.getX());
			int columnIndex;
			if (viewIndex == -1) {
				return;
			}
			columnIndex = columnModel.getColumn(viewIndex).getModelIndex();

			SortOrder order = model.getSortDirection(columnIndex);

			if (order != SortOrder.NONE) {
				// this column allready has a sort on it.
				// we will reverse it's direction.

				if (!e.isControlDown()) {
					// if control isn't down we clear all the sorts
					// then reverse the direction
					model.clearAllSorts();
				}
				// just reverse the sort direction
				if (order == SortOrder.ASCENDING) {

					model.setSort(columnIndex, SortOrder.DESCENDING);

				} else if (order == SortOrder.DESCENDING) {

					model.setSort(columnIndex, SortOrder.ASCENDING);
				}

			} else {
				// this column doesn't have a sort.
				// add an ascending sort.

				if (!e.isControlDown()) {
					// if control isnt down we clear all other sorts.
					model.clearAllSorts();
				}
				model.setSort(columnIndex, SortOrder.ASCENDING);
			}

		}

		/*
		 * (non-Javadoc)
		 *
		 * @see java.awt.event.MouseAdapter#mouseReleased(java.awt.event.MouseEvent)
		 */
		@Override
		public void mouseReleased(final MouseEvent e) {
			handleMouseAction(e);
		}
	}

	protected class MyTableColumnModelListener implements
			TableColumnModelListener {
		boolean enabled;

		public MyTableColumnModelListener() {
			setEnabled(true);
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see javax.swing.event.TableColumnModelListener#columnAdded(javax.swing.event.TableColumnModelEvent)
		 */
		@SuppressWarnings("unused")
		public void columnAdded(final TableColumnModelEvent e) {
			// if(enabled){
			// resizeColumns();
			// enableMenusByColumnCount();
			// }
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see javax.swing.event.TableColumnModelListener#columnMarginChanged(javax.swing.event.ChangeEvent)
		 */
		@SuppressWarnings("unused")
		public void columnMarginChanged(final ChangeEvent e) {
			if (enabled) {
				// User changed column size so we store as the unstretched size
				TableColumn draggedColumn = theTable.getTableHeader()
						.getResizingColumn();
				if (draggedColumn != null) {
					// userChosenColSizes[draggedColumn.getModelIndex()] =
					// draggedColumn
					// .getWidth();
				}
			}
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see javax.swing.event.TableColumnModelListener#columnMoved(javax.swing.event.TableColumnModelEvent)
		 */
		@SuppressWarnings("unused")
		public void columnMoved(final TableColumnModelEvent e) {
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see javax.swing.event.TableColumnModelListener#columnRemoved(javax.swing.event.TableColumnModelEvent)
		 */
		@SuppressWarnings("unused")
		public void columnRemoved(final TableColumnModelEvent e) {
			if (enabled) {
				// resizeColumns();
				// enableMenusByColumnCount();
			}
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see javax.swing.event.TableColumnModelListener#columnSelectionChanged(javax.swing.event.ListSelectionEvent)
		 */
		@SuppressWarnings("unused")
		public void columnSelectionChanged(final ListSelectionEvent e) {
		}

		/**
		 * Set whether this listener should respond to events.
		 *
		 * @param enabled
		 *            True to enable this listener. False otherwise.
		 */
		public void setEnabled(final boolean enabled) {
			this.enabled = enabled;
		}
	}

	@SuppressWarnings("serial")
	public class LeftCornerScrollPaneLayout extends ScrollPaneLayout {
		@Override
		public void layoutContainer(final Container parent) {
			super.layoutContainer(parent);
			if (lowerLeft != null) {
				JScrollPane sp = (JScrollPane) parent;
				Dimension componentDimension = lowerRight != null ? lowerRight
						.getPreferredSize() : lowerLeft.getPreferredSize();
				Rectangle viewportBounds = sp.getViewport().getBounds();
				int componentWidth = Math.min(viewportBounds.width,
						componentDimension.width);
				JScrollBar scrollBar = sp.getHorizontalScrollBar();
				Rectangle scrollBarBounds = scrollBar.getBounds();
				lowerLeft.setBounds(viewportBounds.x, scrollBarBounds.y,
						componentWidth, scrollBarBounds.height);
				scrollBarBounds.width -= componentWidth;
				scrollBarBounds.x += componentWidth;
				scrollBar.setBounds(scrollBarBounds);
			}
		}
	}

	/**
	 * Event handler for the table filter row.
	 */
	protected class FilterRowEditListener implements DocumentListener {


		public void insertUpdate(final DocumentEvent e) {
			handleFilterCriteriaInput();
		}

		public void removeUpdate(final DocumentEvent e) {
			handleFilterCriteriaInput();
		}

		public void changedUpdate(final DocumentEvent e) {
//			handleFilterCriteriaInput();
		}

		/**
		 * When the user enters criteria into the filter field apply the filter
		 * to the dataset mapping.
		 */
		protected void handleFilterCriteriaInput() {
			int editingColumn = theTable.getEditingColumn();
			int editingRow = theTable.getEditingRow();
			editingColumn = theTable.convertColumnIndexToModel(editingColumn);
			if (editingColumn != -1 && editingRow != -1) {
				String value = ((JTextComponent) theTable.getEditorComponent()).getText();
				model.setFilter(editingColumn, value);
			}

		}

	}

}